package main

import (
	"fmt"
	"runtime"
	"strconv"
	"sync"
	"time"
)

var wg sync.WaitGroup

// 定义全局变量 票库存为10张
var ticket int = 10

// 定义一个锁  Mutex 锁头
var mutex sync.Mutex

func main() {
	// goroutine ： 和普通方法调用完全不同，它是并发执行的，快速交替。
	//go hello()
	//for i := 0; i < 1000; i++ {
	//	fmt.Println("main - ", i)
	//}

	// go调度
	// goroutine是竞争cpu的  ，调度
	//go func() {
	//	for i := 0; i < 5; i++ {
	//		fmt.Println("goroutine", i)
	//	}
	//}()
	//for i := 0; i < 5; i++ {
	//	// gosched:礼让, 让出时间片，让其他的 goroutine 先执行
	//	// cpu是随机，相对来说，可以让一下，但是不一定能够成功
	//	// schedule
	//	runtime.Gosched()
	//	fmt.Println("main-", i)
	//}

	// 终止协程
	//go func() {
	//	fmt.Println("start")
	//	test01()
	//	fmt.Println("end")
	//}()
	//time.Sleep(time.Second * 2)

	// 解决并发问题
	// 单线程不存在问题，多线程资源争抢就出现了问题
	//wg.Add(4)
	//go saleTickets("张三")
	//go saleTickets("李四")
	//go saleTickets("王五")
	//go saleTickets("赵六")
	//wg.Wait()

	// channel
	//channelTest01()
	//channelTest02()
	//channelTest03()
	//channelTest04()
	//channelTest05()
	channelTest06()
}

func channelTest06() {
	time.AfterFunc(time.Second*3, mail)
	time.Sleep(5 * time.Second)
}

func mail() {
	// 发邮件
	fmt.Println("发邮件")
}

/*
select：
（1）每一个case必须是一个通道的操作  <-
（2）所有chan操作都有要结果（通道表达式都必须会被求值）
（3）如果任意的通道拿到了结果。它就会立即执行该case、其他就会被忽略
（4）如果有多个case都可以运行，select是随机选取一个执行，其他的就不会执行。
（5）如果存在default，执行该语句，如果不存在，阻塞等待 select 直到某个通道可以运行。
*/
func channelTest05() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		time.Sleep(time.Second * 2)
		ch1 <- 100
	}()

	go func() {
		time.Sleep(time.Second * 2)
		ch2 <- 200
	}()

	// 读取chan数据，无论谁先放入，我们就用谁，抛弃其他的.
	// select 和 swtich 只是在通道中使用，case表达式需要是一个通道结果
	select {
	case num1 := <-ch1:
		fmt.Println(num1)
	case num2 := <-ch2:
		fmt.Println(num2)
		//default:
		// fmt.Println("default")
	}
}

/*
双向通道：channel 是用来实现 goroutine 通信的。一个写、一个读、这是双向通道。
单向通道：只能读或者只能写
*/
func channelTest04() {
	ch1 := make(chan int) // 可读可写
	go writeOnly(ch1)
	go readOnly(ch1)
	time.Sleep(time.Second * 3)
}

// 作为函数的参数或者返回值之类的。
// 指定函数去写，不让他读取，防止通道滥用
func writeOnly(ch chan<- int) {
	// 函数的内部，处理一些写数据的操作
	ch <- 100
}

// 指定函数去读，不让他写，防止通道滥用
func readOnly(ch <-chan int) int {
	// 取出通道的值，做一些操作，不可写的。
	data := <-ch
	fmt.Println(data)
	return data
}

/*
非缓冲通道：chan ， 只能存放一个数据，发送和接受都是阻塞的。一次发送对应一个接收。
缓冲通道：通道带了一个缓冲区，发送的数据直到缓冲区填满为止，才会被阻塞，接收的也是，只有缓冲区清空，才会阻塞。
*/
func channelTest03() {
	//ch2 := make(chan string, 5)
	//fmt.Println(cap(ch2), len(ch2))
	//ch2 <- "1"
	//fmt.Println(cap(ch2), len(ch2)) // 5 1 , 可以通过len来判断缓冲通道中的数据数量
	//ch2 <- "2"
	//ch2 <- "3"
	//fmt.Println(cap(ch2), len(ch2)) // 5 3
	//ch2 <- "4"
	//ch2 <- "5"
	//fmt.Println(cap(ch2), len(ch2)) // 5 5
	//data := <-ch2
	//ch2 <- "6" // deadlock!
	//fmt.Println(data)

	ch3 := make(chan string, 4)
	go func(ch chan string) {
		for i := 0; i < 10; i++ {
			ch <- "test - " + strconv.Itoa(i)
			fmt.Println("子goroutine放入数据：", "test - "+strconv.Itoa(i))
		}
		close(ch)
	}(ch3)
	fmt.Println("--------------------------")
	for s := range ch3 {
		time.Sleep(time.Second)
		fmt.Println("main中读取的数据:", s)
	}
}

/*
关闭通道：告诉接收方，我不会再有其他数据发送到chan了。
*/
func channelTest02() {
	ch1 := make(chan int)

	go func(ch chan int) {
		for i := 0; i < 10; i++ {
			ch <- i
		}
		// 关闭通道，告诉接收方，不会在往ch中放入数据
		close(ch)
	}(ch1)

	//for {
	//	time.Sleep(time.Second)
	//	// ok 判断chan的状态是否是关闭，如果是关闭，不会再取值了。
	//	// ok 如果是true，就代表我们还在读数据；如果是fasle，就说明该通道已关闭
	//	data, ok := <-ch1
	//	if !ok {
	//		fmt.Println("读取完毕", ok)
	//		break
	//	}
	//	fmt.Println("ch1 data:", data)
	//}
	// 读取chan中的数据, for 一个个取，并且会自动判断chan是否close 迭代器
	for data := range ch1 {
		time.Sleep(time.Second)
		fmt.Println(data)
	}
}

/*
（1）chan只有一个空间容量
（2）一个通道发送和接收数据，默认是阻塞的：当一个数据被发送到通道时，在发送语句中被阻塞，直到另一个Goroutine从该通道读取数据。
（3）通道是goroutine之间的连接，所有通道的发送和接收必须处在不同的goroutine中
（4）使用通道时要考虑的一一个重要因素是死锁。如果Goroutine在一 个通道 上发送数据，那么预计其他的Goroutine应该接收数据。如果这种情况不发生，那么程序将在运行时出现死锁。类似地，如果Goroutine 正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据，否则程序将会死锁。
*/
func channelTest01() {
	// 定一个bool的通道
	var ch chan bool
	ch = make(chan bool)

	// 在一个goroutine中去往通道中放入数据
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("goroutine-", i)
		}
		time.Sleep(time.Second * 3)
		ch <- true
	}()

	// 另一个goroutine可以从通道中取出数据。（线程之间的通信）
	// 阻塞等待ch拿到值。有另外一个goroutine往里放值。
	data := <-ch
	fmt.Println("ch data:", data)
}

// 售票函数
func saleTickets(name string) {
	defer wg.Done()
	for {
		// 在拿到共享资源之前先上锁
		mutex.Lock()
		if ticket > 0 {
			time.Sleep(time.Millisecond * 1)
			fmt.Println(name, "剩余票的数量为：", ticket)
			ticket--
		} else {
			mutex.Unlock()
			fmt.Println("票已售完")
			break
		}
		// 操作完毕后，解锁
		mutex.Unlock()
	}
}

func test01() {
	defer fmt.Println("test defer")
	runtime.Goexit() // 终止当前协程
	fmt.Println("test")
}

// hello函数
func hello() {
	for i := 0; i < 1000; i++ {
		fmt.Println("hello - ", i)
	}
}
